#include <stdbool.h>
#include <avr/io.h>
#include <stdlib.h>
#include <avr/pgmspace.h>
#include <uzebox.h>
#include "rooms.c"
#include "MathHelper.c"
#include "data/world_tiles.inc"
#include "data/sprite_tiles.c"
#include "data/fonts_8x8.pic.inc"

#define TILE_WIDTH 8
#define TILE_HEIGHT 8
#define PLAYER_WIDTH 8
#define PLAYER_HEIGHT 8
#define SCREEN_ROWS 28
#define SCREEN_COLS 28
#define SCREEN_WIDTH2 SCREEN_COLS * TILE_WIDTH / 2;
#define SWING_GRAVITY 25;

enum eMoveState {
	MOVE_FEET,
	MOVE_GRAPPLE
};

enum eDirection {
	UP,
	DOWN,
	LEFT,
	RIGHT
};

struct camera {
	struct Vector2 oldPos;
	struct Vector2 newPos;
	int scrollX;
};

struct grapplingHook {
	struct Vector2 wPos;
	int maxLength;	//the maximum distance the hook is allow to travel
	int length;		//the current deployed length of the hook
	int velocity;	//how much the hook moves per frame
	int direction;	//which direction the hook was fired in
	bool fUpdate;	//should we update the hook this frame?
	bool hasContact;
};

struct player {
	struct Vector2_FixedU10_6	wPos;	//the xy position of the player as a scaled integer
	struct Vector2 	lVel;	//the xy velocity of the player as a scaled integer
	float rVel;				//rotational velocity of the player, when grappling
	int rVelMax;			//maximum rotation velocity
	int moveType;	//should maybe be a char?
	bool hasGroundContact;
	bool hasGrapplingHook;
};

struct room {
	struct Vector2 entry;	//where the player is spawned, on entry or death
	struct Vector2 exit;	//an imaginary line the player must cross to load the next level
	char *data;			//pointer to the level data
};


//FUNCTION PROTOTYPES
void drawScreen( char map[], int startCol, int startRow );
void gameLoop();

//map stuff
void getVStrip( char map[], int dir );
int getNumRows( struct room *pRoom );
int getNumCols( struct room *pRoom );

void handleInput();
void movePlayer();
void moveFeet();
void moveGrapple();
void moveHook();

void fireGrapplingHook();
void releaseGrapplingHook();

void moveCamera();
void setLevel( int i );
struct player player1;		//create the player
void initData();
void placePlayer( bool atEntry );
bool feetCollide();
bool isSolid();	//TODO, make it a macro

// TODO zero camera
// const struct camera ZERO_CAMERA

struct camera cam;			//create a camera
struct grapplingHook hook1;	//create a grappling hook

//INPUT
int btnHeld = 0;     	// buttons that are held right now
int btnPressed = 0;  	// buttons that were pressed this frame
int btnReleased = 0; 	// buttons that were released this frame
int btnPrev = 0;		//last button pressed

//MAP stuff
struct room *rooms[2];	//array of pointers to the different levels
struct room *pCurMap;	//the current level
int iCurRoom = 0;		//index of the current level (why are both needed?)
int curMapWidth;		//the width of the current map (is it useful to have this precomputed?)
static int srcX=28;		//the column position of the map we are going to read from
static char destX=31;	//the memory position we write to (0-31)

int screenWidth2 = SCREEN_COLS * TILE_WIDTH / 2;	//useful to have precomputed
short hookAngle = 0;
int slowDown = 0;

int main(){

	SetTileTable(world_tiles);
	SetSpritesTileTable(sprite_tiles);
	ClearVram();

	SetFontTilesIndex(17);
	initData();	//Wow, trying to init ANY GLOBAL SCOPED VAR inside this function totally fucks the application

	player1.lVel.x = 0;
	player1.lVel.y = 0;
	player1.rVel = 0;
	player1.rVelMax = 4;
	player1.hasGroundContact = false;
	//XXX for debugging
	player1.hasGrapplingHook = true;

	hook1.wPos.x = 1;
	hook1.wPos.y = 1;
	hook1.velocity = 3;
	hook1.maxLength = 100;
	hook1.length = 1;
	hook1.fUpdate = false;

	//setup the sprites, this needs to happen before setLevel is called
	//for some reason I don't know.
	//player sprite ->
	sprites[0].tileIndex = 1;
	sprites[0].x = 0;
	sprites[0].y = 0;

	//grappling hook sprite
	sprites[1].tileIndex = 1;
	sprites[1].x = 50;
	sprites[1].y = 50;

	SetSpriteVisibility(true);

	setLevel(0);
	placePlayer(true);

	while(1){
		WaitVsync(1);
		gameLoop();
	}
}

/**
 * create each room, set is map data, and its entry and exit points
 * zero out the player struct
 */
void initData() {
	//setup some rooms
	struct room roomA;
	roomA.entry.x = 46;
	roomA.entry.y = 0;
	roomA.exit.x = 989;
	roomA.exit.y = 154;
	roomA.data = room0;
	rooms[0] = &roomA;

	struct room roomB;
	roomB.entry.x = 0;
	roomB.entry.y = 154;
	roomB.exit.x = 980;
	roomB.exit.y = 154;
	roomB.data = Tiles1_room1;
	rooms[1] = &roomB;

	//Cant init this here, totally fucks the application for unknown reason
	//player1.wPos.x = 0;
}

void gameLoop() {
	handleInput();
	moveCamera();
	movePlayer();
	moveHook();
}

void handleInput() {

	btnHeld = ReadJoypad(0);
    btnPressed = btnHeld & (btnHeld ^ btnPrev);
    btnReleased = btnPrev & (btnHeld ^ btnPrev);
    btnPrev = btnHeld;

    //TODO might need to change how this is handled with different movement types
	switch(player1.moveType){
		case MOVE_FEET:
			moveFeet();
			break;

		case MOVE_GRAPPLE:
			moveGrapple();
			break;
	}
	//some stuff you can do no matter the state of the player
	//Fired a grappling hook?

	if(btnPressed & BTN_A){
		//make the player jump
		if(player1.hasGroundContact){
			player1.lVel.y = -(1 << 11);
			//TEMPORARY, fix this
		}
		//player is already airborn, make them fire the hook
		else {
			//shoot at an angle if moving
			if(btnHeld & BTN_RIGHT){
				fireGrapplingHook(RIGHT);
			}
			else if(btnHeld & BTN_LEFT){
				fireGrapplingHook(LEFT);
			}
			//shoot it straight up if not moving
			else {
				fireGrapplingHook(UP);
			}
		}
	}
	else if(btnReleased & BTN_A){
		releaseGrapplingHook();
	}

	//hook1.wPos.x = player1.wPos.x.part.integer + (sin2(hookAngle) * 20) << 14);
	PrintInt(10,10,sin2(hookAngle), 0);
	if(slowDown++ >= 1){
		hookAngle++;
		player1.wPos.x.full = 4000 + (sin2(hookAngle)>>4);
		slowDown = 0;
	}
}

/**
 * redraws a whole screenfull of gunk, no out of memory checks are perfmored so be your ass careful
 */

//TODO drawscreen needs to be reporgramed to draw from any portion of the array
void drawScreen( char map[], int startCol, int startRow ) {
	unsigned char c;
	unsigned char mapWidth;

	int y2 = 0;
	int x2 = (destX + 1) % 32;	//the col position of video memory we write to (its 1+ the last offscreen written position)
	int cntX = 0;	//counts the number of writes to video memory, when we hit 32, we move down a row

	//square up any excess scrollage
	Scroll(-cam.scrollX, 0);
	cam.scrollX = 0;

	mapWidth = pgm_read_byte( &map[0] );

	for(int y=startRow;y<SCREEN_ROWS;y++){
		//you can set more of these than there are on the screen
		//seems you need to, to avoid a gap when scrolling
		for(int x=startCol;x<startCol + 32;x++){
			c=pgm_read_byte( &(map[(y*mapWidth)+x+2]) );

			//for debugging writes the video memory position on the top row of the screen
			//if(y2 == 0){
			//	SetTile(x2, y2, x2 % 32);
			//}
			//else {
			//	SetTile(x2,y2,c);
			//}

			SetTile(x2, y2, c);

			//wrap the columns
			x2++;
			if(x2 >= 32){
				x2 = 0;
			}

			//check to see if we should move down a row
			//have we made 32 writes to video memory?
			cntX++;
			if(cntX >= 32){
				cntX = 0;
				y2++;
			}
		}
	}
}

/*
 * writes the next column of the map to video memory
 */
void getVStrip( char map[], int dir ) {
	int mapWidth = pgm_read_byte(map);	//width is first byte of this array

	if(dir > 0){
		srcX = cam.newPos.x / 8 + 31;
		destX++;
	}
	else {
		srcX = cam.newPos.x / 8;
		//decrement AFTER the write
	}

	while(destX > 31) destX -= 32;
	while(destX < 0) destX += 32;

	if(srcX>=mapWidth) srcX=0;
	else if(srcX < 0) srcX = mapWidth;

	for(int y=0;y<SCREEN_ROWS;y++){
// FOR DEBUGGING, write the video memory position on the top of the map
//		if(y == 0){
//			SetTile(destX, y, destX % 32);
//		}
//		else {
//			SetTile(destX,y,pgm_read_byte(&(map[(y*mapWidth)+srcX+2])));
//		}

		SetTile(destX,y,pgm_read_byte(&(map[(y*mapWidth)+srcX+2])));
	}

	if(dir < 0){
		destX--;
	}
}

/**
 * has the camera follow the player, but not past the edges of the level
 * keeps track of how much the camera has moved, so that we scroll the
 * screen by that amount
 */
void moveCamera() {
	struct Vector2 d;

	if(player1.wPos.x.part.integer > screenWidth2 && player1.wPos.x.part.integer < curMapWidth * TILE_WIDTH - screenWidth2){
		cam.oldPos = cam.newPos;
		cam.newPos.x = player1.wPos.x.part.integer - screenWidth2;

		d.x = cam.newPos.x - cam.oldPos.x;
		d.y = cam.newPos.y - cam.oldPos.y;

		cam.scrollX += d.x;

		//TODO, acutally calculate how many strips we need to make
		if(cam.scrollX >= TILE_WIDTH){
			cam.scrollX = 0; //-= 8?
			getVStrip( pCurMap->data, 1 );
		}
		else if(cam.scrollX < 0){
			cam.scrollX = 7; //+= 8?
			getVStrip( pCurMap->data, -1 );
		}
		Scroll(d.x, 0);
	}

	//move to an update sprites method?
	//would be better to iterate over the sprites array?
	sprites[0].x = player1.wPos.x.part.integer - cam.newPos.x;
	sprites[0].y = player1.wPos.y.part.integer - cam.newPos.y;
	sprites[1].x = hook1.wPos.x - cam.newPos.x;
	sprites[1].y = hook1.wPos.y - cam.newPos.y;
}

//VERY crude CD routine, but this will do for now
void movePlayer() {
	//do we load a new level? quick check to see if the players x position is too big

	//we want the one right above the feet, not the one were standing on
	//row number may be out of bounds since player can fall to its "death"
	int row = (player1.wPos.y.part.integer + PLAYER_HEIGHT - 1) / TILE_HEIGHT;
	int col = player1.wPos.x.part.integer / TILE_WIDTH;

	//firstly, is player even inside the level bounds? NO?!
	//its most likely fallen to its death, respawn it at the proper location
	if(row >= getNumRows(pCurMap) || col >= getNumCols(pCurMap)){
		placePlayer(true);
	}

	//TEMPORARY next/prev level load check
	if(player1.wPos.x.part.integer > 990 && iCurRoom + 1 < 2){
		setLevel(++iCurRoom);
		placePlayer(true);
	}
	else if(player1.wPos.x.part.integer < 0 && iCurRoom - 1 >= 0){
		setLevel(--iCurRoom);
		placePlayer(false);
	}
}

void moveFeet() {
	//check to see if the feet collide with a block
	if(btnHeld & BTN_LEFT){
		player1.lVel.x = -(1<<6);
	}
	else if(btnHeld & BTN_RIGHT){
		player1.lVel.x = 1<<6;
	}

	if(btnReleased & BTN_LEFT || btnReleased & BTN_RIGHT){
		player1.lVel.x = 0;
	}

	int yDisp;

	//Y collision check
	if(feetCollide()){
		//check if the player is ontop of a block, or inside of it
		//player is NOT on top of block

		yDisp = player1.wPos.y.part.integer % TILE_HEIGHT;
		if(yDisp){
			//move it to proper position
			//kill its velocity
			player1.wPos.y.part.integer -= yDisp;
			player1.lVel.y = 0;
		}
		//player IS on top of block
		else {
			player1.hasGroundContact = true;
		}
	}
	else {
		//TODO optomize
		player1.hasGroundContact = false;
		player1.lVel.y = 1 << 6;
	}


	//x collision check
	int futureX;
	int xDisp;
	int row;
	int col;

	//we want the one right above the feet, not the one were standing on
	//row number may be out of bounds since player can fall to its "death"
	row = (player1.wPos.y.part.integer + PLAYER_HEIGHT - 1) / TILE_HEIGHT;

	//left toe if moving left
	//bitshift arthihmetic on signed values?
	futureX = player1.wPos.x.part.integer + (player1.lVel.x >> 6)- 1;

	//right toe if moving right
	if(player1.lVel.x > 0)
		futureX += PLAYER_WIDTH;

	col = futureX / TILE_WIDTH;

	//Todo, need to check the bounds or row + col before performing this update
	//the row is EASILY out of bounds, since the player is allowed to fall through the level
	//Reading from out of bounds position will cause really bad programming errors
	if(row < getNumRows(pCurMap) && col < getNumCols(pCurMap) && isSolid(row, col)){
		if(player1.lVel.x < 0){
			xDisp = 8 - (futureX % 8);
			//player1.wPos.x += xDisp;
		}
		else{
			//xDisp = futureX % 8;
			//player1.wPos.x -= xDisp;
		}
		//why does this trigger
		player1.lVel.x = 0;
	}

	player1.wPos.x.full += player1.lVel.x;
	player1.wPos.y.full += player1.lVel.y;
}

void moveGrapple() {
	//TODO, what you really want to do here is shorten the legth of the rope
	if(btnHeld & BTN_UP){
		player1.lVel.y = -(1<<6);
	}
	else if(btnHeld & BTN_DOWN){
		player1.lVel.y = 1 << 6;
	}

//	struct Vector2 hookRelative = vector2Subtract(&hook1.wPos, &player1.wPos);
//	if(btnHeld & BTN_LEFT){
//		player1.rVel += 0.005;
//	}
//	else if(btnHeld & BTN_RIGHT){
//		player1.rVel -= 0.005;
//	}
//
//	//clamp rotation values
//	if(player1.rVel > player1.rVelMax)
//		player1.rVel = player1.rVelMax;
//	else if(player1.rVel < -player1.rVelMax)
//		player1.rVel = -player1.rVelMax;
//
//	//Hopefully the optomizing compiler takes care of the multiple lookups
//	//player1.wPos.x = hook1.wPos.x - hookRelative.x * cos2(player1.rVel) + hookRelative.y * sin2(player1.rVel);
//	//player1.wPos.y = hook1.wPos.y - hookRelative.y * cos2(player1.rVel) + hookRelative.x * sin2(player1.rVel);
//
//	//player1.rVel += sin2(90)
}

/**
 * updates the grappling hook each frame
 */
void moveHook() {
	//quit early if the hook is not active
	if(!hook1.fUpdate)
		return;

	hook1.length = 1;
	switch(hook1.direction){

		//TODO, replace with a velocity model
		case UP:
			hook1.wPos.y -= hook1.velocity;
			break;

		case RIGHT:
			hook1.wPos.x += hook1.velocity;
			hook1.wPos.y -= hook1.velocity;
			break;

		case LEFT:
			hook1.wPos.x -= hook1.velocity;
			hook1.wPos.y -= hook1.velocity;
			break;
	}

	//check to see if the hook has collided with anything
	//TODO, replace with bitshift division
	int col = hook1.wPos.x / TILE_WIDTH;
	int row	= hook1.wPos.y / TILE_HEIGHT;

	//hook has hit something
	//set the player movement type to grappled
	//stop updating the hook
	//TODO remember to span BOTH left and right boundaries of the hook
	if(pgm_read_byte(&pCurMap->data[row*curMapWidth+col+2])){
		//player1.moveType = MOVE_GRAPPLE;
		hook1.fUpdate = false;
	}

	//check to see if we've reached the max length of the hook
	if(hook1.length >= hook1.maxLength){
		hook1.fUpdate = false;
	}
}

/**
 * positions the grappling hook at the players current position
 * and sets the update flag. see moveHook();
 */
void fireGrapplingHook( int direction ){

	//quit early if the player hasnt picked up the grappling hook yet
	if(!player1.hasGrapplingHook)
		return;

	//position the hook where the player is
	//reset its length to zero
	hook1.length = 0;
	hook1.wPos.x = player1.wPos.x.part.integer;
	hook1.wPos.y = player1.wPos.y.part.integer;
	hook1.direction = direction;
	hook1.fUpdate = true;

	//show it
	sprites[1].tileIndex = 1;
}

/**
 * hides the hook, and returns the players move type to his feet
 */
void releaseGrapplingHook(){
	hook1.fUpdate = false;
	player1.rVel = 0;	//reset the rotational velocity for next time
	player1.moveType = MOVE_FEET;

	//hide it
	sprites[1].tileIndex = 1;
}

bool feetCollide() {
	//see if the player is making contact with the ground

	//find the number of column we have to check
	unsigned int col;
	unsigned int row;

	col = player1.wPos.x.part.integer / TILE_WIDTH;
	//n = col - (player1.wPos.x + PLAYER_WIDTH) / TILE_WIDTH;
	row = (player1.wPos.y.part.integer + PLAYER_HEIGHT) / TILE_HEIGHT;

	return pgm_read_byte(&pCurMap->data[row * curMapWidth + col + 2]);
}

int getNumCols( struct room *pMap ){
	return pgm_read_byte(&pMap->data[0]);
}

int getNumRows( struct room *pMap ){
	return pgm_read_byte(&pMap->data[1]);
}

bool isSolid( int row, int col ) {
	return pgm_read_byte(&pCurMap->data[row * curMapWidth + col + 2]);
}

void setLevel( int i ) {

	//scroll the remainder of the distance, so we start the level on an even column position
	Scroll(-cam.scrollX, 0);
	cam.scrollX = 0;

	pCurMap = rooms[i];
	curMapWidth = getNumCols(pCurMap);

	//rest camera
	//destX = 0;
	cam.scrollX = 0;
	cam.newPos.x = 0;
	cam.oldPos.x = 0;
}

/**
 * places the player at the begining or at the end of the room,
 * also draws that section of the screen
 */
void placePlayer( bool atEntry ){
	if(atEntry){
		player1.wPos.x.part.integer = pCurMap->entry.x;
		player1.wPos.y.part.integer = pCurMap->entry.y;
		drawScreen( pCurMap->data, 0, 0 );

		cam.newPos.x = cam.oldPos.x = 0;
		cam.newPos.y = cam.oldPos.y = 0;
	}
	else {
		player1.wPos.x.part.integer = pCurMap->exit.x;
		player1.wPos.y.part.integer = pCurMap->exit.y;
		drawScreen( pCurMap->data, curMapWidth - SCREEN_COLS, 0);

		//TODO, you need to figure out a more elegant way to place the camera at the exit
		cam.newPos.x = cam.oldPos.x = (curMapWidth - SCREEN_COLS) * TILE_WIDTH;
		cam.newPos.y = cam.oldPos.y = 0;
	}
}
